﻿using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
using AzukiMap.Tools.Logger;

namespace AzukiMap.Map
{
    public delegate void ScreenScaleChangedHandler(object sender, ScreenScaleChangedArgs e);

    public delegate void LayerContainerMovedHandler(object sender, LayerContainerMovedArgs e);

    public delegate void MouseMovedOnScreenHandler(object sender, MouseMovedOnScreenArgs e);

    public delegate void MouseClickedOnScreenHandler(object sender, MouseClickedOnScreenArgs e);

    public delegate void MouseRangeBoxHandler(object sender, MouseRangeBoxArgs e);

    public delegate void MouseFeatureEditHandler(object sender, MouseFeatureEditArgs e);

    public delegate bool CheckFeatureDragHandler(object sender);

    public delegate void MouseFeatureDragHandler(object sender, MouseFeatureDragArgs e);

    public class MapMouseControl
    {
        private const double ZOOM_IN_RATE = 1.2;
        private const double ZOOM_OUT_RATE = 0.8;
        private const long ZOOM_FINISHED_TIMERAG = 10000000; // 1 sec
        private readonly TransformGroup _layerTransform;
        private readonly TranslateTransform _moveTransform;
        private readonly Canvas _rootCanvas;
        private readonly ScaleTransform _scaleTransform;
        private readonly DispatcherTimer _wheelControlFinishTimer;

        private double _adjX;
        private double _adjY;
        private Point _currentPoint;
        private double _currentScale;
        private Point _dragStartPoint;
        private bool _isLocked;
        private bool _isMoving;
        private Point _lastClickPoint;
        private Canvas _layerContainer;
        private MapMouseMode _mapMouseMode = MapMouseMode.DragMove;

        private double _maxScale = double.MaxValue;
        private double _minScale;
        private bool _enabled = true;
        private bool _isWheelMoving;

        private Point _moveSpan;
        private Point _moveSrc;
        private long _moveStartTime;

        private const double MOVE_TIME = 2000;

        public event ScreenScaleChangedHandler ScreenScaleChanged;
        public event LayerContainerMovedHandler LayerContainerMoved;
        public event MouseMovedOnScreenHandler MouseMovedOnScreen;
        public event MouseClickedOnScreenHandler MouseClickedOnScreen;
        public event MouseRangeBoxHandler MouseRangeBox;
        public event MouseFeatureEditHandler MouseFeatureEdit;
        public event CheckFeatureDragHandler CheckFeatureDrag;
        public event MouseFeatureDragHandler MouseFeatureDrag;


        internal MapMouseControl(Canvas rootCanvas)
        {
            Logger.All("MapMouseControl.MapMouseControl: Init start.");

            _rootCanvas = rootCanvas;
            _layerContainer = PrepareLayerContainer();
            AddRootCanvasEvents();

            _moveTransform = new TranslateTransform();
            _scaleTransform = new ScaleTransform();
            _layerTransform = new TransformGroup();
            _layerTransform.Children.Add(_moveTransform);
            _layerTransform.Children.Add(_scaleTransform);

            _moveTransform.X = 0;
            _moveTransform.Y = 0;
            _scaleTransform.ScaleX = 1.0;
            _scaleTransform.ScaleY = 1.0;

            _isMoving = false;
            _isLocked = false;
            _dragStartPoint = new Point(0, 0);

            _wheelControlFinishTimer = new DispatcherTimer();
            _wheelControlFinishTimer.Tick += WheelControlFinishTimer_Tick;

            _adjX = 0;
            _adjY = 0;

            Logger.All("MapMouseControl.MapMouseControl: Init finish.");
        }

        public bool Enabled
        {
            get { return _enabled; }
            set { _enabled = value; }
        }

        public MapMouseMode MapMouseMode
        {
            get { return _mapMouseMode; }
            set { _mapMouseMode = value; }
        }

        /// <summary>
        /// Whether Create mode or no
        /// </summary>
        public bool IsFeatureCreate
        {
            get
            {
                switch (MapMouseMode)
                {
                    case MapMouseMode.CreatePolygon:
                        return true;
                    case MapMouseMode.CreateRectangle:
                        return true;
                    case MapMouseMode.CreatePoint:
                        return true;
                }
                return false;
            }
        }

        /// <summary>
        /// Whether Edit mode or no
        /// </summary>
        public bool IsFeatureEdit
        {
            get
            {
                switch (MapMouseMode)
                {
                    case MapMouseMode.EditPolygon:
                        return true;
                }
                return false;
            }
        }

        public Point LastClickPoint
        {
            get { return _lastClickPoint; }
        }

        public bool IsLocked
        {
            get { return _isLocked; }
            set { _isLocked = value; }
        }

        public Canvas LayerContainer
        {
            get { return _layerContainer; }
            set { _layerContainer = value; }
        }

        internal double MaxScale
        {
            get { return _maxScale; }
            set { _maxScale = value; }
        }

        internal double MinScale
        {
            get { return _minScale; }
            set { _minScale = value; }
        }

        internal double CurrentScale
        {
            get { return _currentScale; }
            set { _currentScale = value; }
        }

        protected virtual void OnContainerMoved(LayerContainerMovedArgs e)
        {
            if (LayerContainerMoved != null)
            {
                LayerContainerMoved(this, e);
            }
        }

        protected virtual void OnScaleChanged(ScreenScaleChangedArgs e)
        {
            if (ScreenScaleChanged != null)
            {
                ScreenScaleChanged(this, e);
            }
        }

        protected virtual void OnMouseMoved(MouseMovedOnScreenArgs e)
        {
            if (MouseMovedOnScreen != null)
            {
                MouseMovedOnScreen(this, e);
            }
        }

        protected virtual void OnMouseClick(MouseClickedOnScreenArgs e)
        {
            if (MouseClickedOnScreen != null)
            {
                MouseClickedOnScreen(this, e);
            }
        }

        protected virtual void OnMouseRangeBox(MouseRangeBoxArgs e)
        {
            if (MouseRangeBox != null)
            {
                MouseRangeBox(this, e);
            }
        }

        protected virtual void OnMouseFeatureEdit(MouseFeatureEditArgs e)
        {
            if (MouseFeatureEdit != null)
            {
                MouseFeatureEdit(this, e);
            }
        }

        protected virtual bool OnCheckFeatureDrag()
        {
            if (CheckFeatureDrag != null)
            {
                return CheckFeatureDrag(this);
            }
            return false;
        }

        protected virtual void OnMouseFeatureDrag(MouseFeatureDragArgs e)
        {
            if (MouseFeatureDrag != null)
            {
                MouseFeatureDrag(this, e);
            }
        }

        private Canvas PrepareLayerContainer()
        {
            Logger.All("MapControl.PrepereLayerContainer");

            var layerContainer = new Canvas();
            _rootCanvas.Children.Add(layerContainer);

            return layerContainer;
        }

        internal void MoveTo(Point p)
        {
            if (_isMoving && !_isLocked)
            {
                Logger.All("MapMouseControl.MoveTo: " + p);

                if (_wheelControlFinishTimer.IsEnabled)
                    _wheelControlFinishTimer.Stop();

                double moveX = _dragStartPoint.X - p.X;
                double moveY = _dragStartPoint.Y - p.Y;
                _moveTransform.X = _adjX - moveX;
                _moveTransform.Y = _adjY - moveY;
                Transform();
            }
        }

        public void AutoMoveStart(Point src, Point dst)
        {
            if (_isMoving)
            {
                AutoMoveFinished();
            }

            _dragStartPoint = src;
            _moveSrc = src;
            _moveSpan = new Point(dst.X - src.X, dst.Y - src.Y);
            _moveStartTime = DateTime.Now.Ticks;
            CompositionTarget.Rendering += CompositionTarget_Rendering;
            _isMoving = true;
        }

        private void CompositionTarget_Rendering(object sender, EventArgs e)
        {
            AutoMoveTo();
        }

        public void AutoMoveTo()
        {
            var msec = (long) ((DateTime.Now.Ticks - _moveStartTime)/MOVE_TIME);
            if (msec > MOVE_TIME)
            {
                AutoMoveFinished();
            }
            double stepX = _moveSpan.X*msec/MOVE_TIME;
            double stepY = _moveSpan.Y*msec/MOVE_TIME;

            var p = new Point(_moveSrc.X - stepX, _moveSrc.Y - stepY);
            MoveTo(p);
            return;
        }

        public void AutoMoveFinished()
        {
            CompositionTarget.Rendering -= CompositionTarget_Rendering;

            var p = new Point(_moveSrc.X - _moveSpan.X, _moveSrc.Y - _moveSpan.Y);

            MoveTo(p);
            _adjX = _moveTransform.X;
            _adjY = _moveTransform.Y;
            _isMoving = false;

            MoveFinished(p);

            return;
        }

        public void GetAdjustXY(out double x, out double y)
        {
            x = _moveTransform.X;
            y = _moveTransform.Y;
        }

        private void ZoomByWheel(double delta)
        {
            if (!_isLocked)
            {
                _isWheelMoving = true;

                if (_wheelControlFinishTimer.IsEnabled)
                    _wheelControlFinishTimer.Stop();

                double zoomRatio = 1.0;
                if (delta < 0)
                {
                    zoomRatio *= ZOOM_OUT_RATE;
                }
                else
                {
                    zoomRatio *= ZOOM_IN_RATE;
                }

                ZoomByRatio(zoomRatio, _currentPoint);

                Logger.All(
                    "MapMouseControl.ZoomByWheel: "
                    + " delta = " + delta
                    + " zoom = " + zoomRatio
                    + " _currentPoint = " + _currentPoint
                    );

            }
        }

        public void ZoomByRatio(double zoomRatio)
        {
            var center = new Point(_rootCanvas.ActualWidth/2, _rootCanvas.ActualHeight/2);
            ZoomByRatio(zoomRatio, center);
        }

        private void ZoomByRatio(double zoomRatio, Point zoomAncher)
        {
                double newScale = GetTransformScale(zoomRatio);

                _scaleTransform.ScaleX = newScale;
                _scaleTransform.ScaleY = newScale;
                _scaleTransform.CenterX = zoomAncher.X;
                _scaleTransform.CenterY = zoomAncher.Y;
            
                Transform();
                _wheelControlFinishTimer.Interval = new TimeSpan(ZOOM_FINISHED_TIMERAG);
                _wheelControlFinishTimer.Start();
        }

        private double GetTransformScale(double zoomRatio)
        {
            double newScale = _scaleTransform.ScaleX*zoomRatio;

            // check min ratio.
            double minZoomRatio = _currentScale/_maxScale;
            if (newScale < minZoomRatio)
            {
                newScale = minZoomRatio;
            }

            // check max ratio.
            double maxZoomRatio = _currentScale/_minScale;
            if (maxZoomRatio < newScale)
            {
                newScale = maxZoomRatio;
            }
            return newScale;
        }

        private void Transform()
        {
            Logger.All("MapMouseControl.Transform");

            _layerContainer.RenderTransform = _layerTransform;
        }

        private void MoveFinished(Point p)
        {
            double moveX = _dragStartPoint.X - p.X;
            double moveY = _dragStartPoint.Y - p.Y;
            if (moveX == 0 && moveY == 0)
            {
                return;
            }

            Logger.All(
                "MapMouseControl.MoveFinished: "
                + " p = " + p
                + " moveX = " + moveX
                + " moveY = " + moveY
                );

            var e = new LayerContainerMovedArgs(moveX, moveY, _adjX, _adjY);
            OnContainerMoved(e);
        }

        private void ZoomFinished()
        {
            double anchorX = _scaleTransform.CenterX;
            double anchorY = _scaleTransform.CenterY;
            double scaleRatio = _scaleTransform.ScaleX;

            Logger.All(
                "MapMouseControl.ZoomFinished: "
                + " anchorX = " + anchorX
                + " anchorY = " + anchorY
                + " scaleRatio = " + scaleRatio
                );

            var e = new ScreenScaleChangedArgs(scaleRatio, anchorX, anchorY, 0, 0);
            OnScaleChanged(e);

            Refresh();
        }

        internal void Refresh()
        {
            Logger.All("MapMouseControl.Refresh");

            ResetTransforms();
        }

        private void ResetTransforms()
        {
            _scaleTransform.ScaleX = 1.0;
            _scaleTransform.ScaleY = 1.0;
            _moveTransform.X = 0;
            _moveTransform.Y = 0;
            _adjX = 0;
            _adjY = 0;
        }


        private void AddRootCanvasEvents()
        {
            _rootCanvas.MouseLeftButtonDown += RootCanvas_MouseLeftButtonDown;
            _rootCanvas.MouseLeftButtonUp += RootCanvas_MouseLeftButtonUp;
            _rootCanvas.MouseMove += RootCanvas_MouseMove;
            _rootCanvas.MouseLeave += RootCanvas_MouseLeave;
            _rootCanvas.MouseWheel += RootCanvas_MouseWheel;
            _rootCanvas.MouseRightButtonDown += RootCanvas_MouseRightButtonDown;
        }

        private void MouseLeftButtonDown(Point p)
        {
            Logger.All(
                "MapMouseControl.MouseLeftButtonDown:"
                + " p = " + p
                );

            if (_isMoving)
            {
                return;
            }

            _dragStartPoint = p;

            if (OnCheckFeatureDrag())
            {
                return;
            }

            if (_mapMouseMode == MapMouseMode.DragMove  
                // || _mapMouseMode == MapMouseMode.DragRange || 
                // IsFeatureCreate || 
                // IsFeatureEdit 
                )
            {
                _isMoving = true;
            }
        }

        private void MouseMove(Point p)
        {
            Logger.All(
                "MapMouseControl.MouseMove:"
                + " p = " + p
                );

            if (_mapMouseMode == MapMouseMode.DragRange)
            {
                if (_isMoving && !_isLocked)
                {
                    // Capture root canvas for mouse
                    _rootCanvas.CaptureMouse();
                    var args = new MouseRangeBoxArgs(MouseRangeBoxStatus.SizeChanged);
                    args.Start = _dragStartPoint;
                    args.End = p;
                    OnMouseRangeBox(args);
                    return;
                }
            }

            else if (OnCheckFeatureDrag())
            {
                if (!_isLocked)
                {
                    // Capture root canvas for mouse
                    _rootCanvas.CaptureMouse();
                    var args = new MouseFeatureDragArgs(MouseFeatureDragStatus.Move, p.X, p.Y);
                    OnMouseFeatureDrag(args);
                }
            }
                //else if (_mapMouseMode == MapMouseMode.DragMove || IsFeatureCreate || IsFeatureEdit)
            else if (_mapMouseMode == MapMouseMode.DragMove || IsFeatureEdit)
            {
                if (_isMoving && !_isLocked && !_wheelControlFinishTimer.IsEnabled)
                {
                    // Capture root canvas for mouse
                    _rootCanvas.CaptureMouse();
                    MoveTo(p);
                }
            }
            else if (IsFeatureCreate)
            {
                if (!_isMoving && !_isLocked)
                {
                    // Capture root canvas for mouse
                    _rootCanvas.CaptureMouse();
                    var args = new MouseFeatureEditArgs(MouseFeatureEditStatus.Move);
                    args.Point = p;
                    OnMouseFeatureEdit(args);
                }
            }

            if (MouseMovedOnScreen != null)
            {
                OnMouseMoved(new MouseMovedOnScreenArgs(p.X, p.Y));
            }
        }

        private void MouseClick(Point p)
        {
            Logger.All(
                "MapMouseControl.MouseClick:"
                + " p = " + p
                );

            /*
            if (IsFeatureCreate)
            {
                var args = new MouseFeatureEditArgs(MouseFeatureEditStatus.Click);
                args.Point = p;
                OnMouseFeatureEdit(args);
            }
             */

            if (MouseClickedOnScreen != null)
            {
                _lastClickPoint = p;
                OnMouseClick(new MouseClickedOnScreenArgs(p.X, p.Y));
            }
        }

        private void MouseLeftButtonUp(Point p)
        {
            Logger.All(
                "MapMouseControl.MouseMove:"
                + " p = " + p
                );

            if (OnCheckFeatureDrag())
            {
                var args = new MouseFeatureDragArgs(MouseFeatureDragStatus.Fixed, p.X, p.Y);
                OnMouseFeatureDrag(args);

                // release mouse capture
                _rootCanvas.ReleaseMouseCapture();
                return;
            }


            if (_mapMouseMode == MapMouseMode.DragRange)
            {
                // release mouse capture
                _rootCanvas.ReleaseMouseCapture();

                _isMoving = false;

                var args = new MouseRangeBoxArgs(MouseRangeBoxStatus.Fixed);
                args.Start = _dragStartPoint;
                args.End = p;

                OnMouseRangeBox(args);
            }

            else if (_mapMouseMode == MapMouseMode.DragMove || IsFeatureCreate || IsFeatureEdit)
            {
                if (_isMoving)
                {
                    MoveTo(p);
                    _adjX = _moveTransform.X;
                    _adjY = _moveTransform.Y;
                    _isMoving = false;

                    MoveFinished(p);
                }

                // release mouse capture
                _rootCanvas.ReleaseMouseCapture();
            }
            if (IsFeatureCreate)
            {
                var args = new MouseFeatureEditArgs(MouseFeatureEditStatus.Click);
                args.Point = p;
                OnMouseFeatureEdit(args);
            }
            if (! _isMoving)
            {
                return;
            }
        }

        private void MouseLeave()
        {
            Logger.All(
                "MapMouseControl.MouseLeave:"
                );
        }

        private void RootCanvas_MouseWheel(object sender, MouseWheelEventArgs e)
        {
            if (!_enabled) return;

            double delta = e.Delta;
            ZoomByWheel(delta);
        }

        private void RootCanvas_MouseMove(object sender, MouseEventArgs e)
        {
            if (!_enabled || _isWheelMoving) return;

            Point pos = e.GetPosition((UIElement) sender);
            _currentPoint = pos;
            MouseMove(pos);
        }

        private void RootCanvas_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            if (!_enabled || _isWheelMoving) return;

            Point pos = e.GetPosition((UIElement) sender);
            MouseLeftButtonUp(pos);

            if (_dragStartPoint == pos)
            {
                MouseClick(pos);
            }
        }

        private void RootCanvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (!_enabled || _isWheelMoving) return;

            Point pos = e.GetPosition((UIElement) sender);
            MouseLeftButtonDown(pos);
        }

        private void RootCanvas_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (IsFeatureCreate || IsFeatureEdit)
            {
                Point pos = e.GetPosition((UIElement) sender);
                var args = new MouseFeatureEditArgs(MouseFeatureEditStatus.Fixed);
                args.Point = pos;
                OnMouseFeatureEdit(args);
                e.Handled = true;
            }
        }

        private void RootCanvas_MouseLeave(object sender, MouseEventArgs e)
        {
            MouseLeave();
        }

        private void WheelControlFinishTimer_Tick(object sender, EventArgs e)
        {
            _isWheelMoving = false;

            _wheelControlFinishTimer.Stop();
            ZoomFinished();
        }
    }

    public enum MapMouseMode
    {
        DragMove,
        DragRange,
        CreatePoint,
        CreatePolygon,
        CreateRectangle,
        EditPolygon,
        ZoomIn,
        ZoomOut,
        None
    }

    public class MouseLock : IDisposable
    {
        private readonly bool enable;
        private readonly MapMouseControl mmc;

        public MouseLock(MapMouseControl mmc)
        {
            this.mmc = mmc;
            enable = mmc.Enabled;
            mmc.Enabled = false;
        }

        #region IDisposable Members

        public void Dispose()
        {
            mmc.Enabled = enable;
        }

        #endregion
    }
}